package com.nageoffer.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.shortlink.admin.common.convention.exception.ClientException;
import com.nageoffer.shortlink.admin.dao.entity.UserDO;
import com.nageoffer.shortlink.admin.dao.mapper.UserMapper;
import com.nageoffer.shortlink.admin.dto.req.UserLoginReqDTO;
import com.nageoffer.shortlink.admin.dto.req.UserRegisterReqDTO;
import com.nageoffer.shortlink.admin.dto.req.UserUpdateReqDTO;
import com.nageoffer.shortlink.admin.dto.resp.UserLoginRespDTO;
import com.nageoffer.shortlink.admin.dto.resp.UserRespDTO;
import com.nageoffer.shortlink.admin.service.UserService;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.nageoffer.shortlink.admin.common.constant.RedisCacheConstant.LOCK_USER_REGISTER_KEY;
import static com.nageoffer.shortlink.admin.common.enums.UserErrorCodeEnum.*;


/*
* 用户接口实现层
* */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO>implements UserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;///Redis依赖


    @Override
    public UserRespDTO getUserByUsername(String username) {
        // 创建查询条件，匹配 UserDO 中 username 属性等于传入的参数
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username);

        // 查询数据库，返回符合条件的 UserDO 对象
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        if(userDO == null) {return null;}
        // 创建 UserRespDTO 对象用于存储结果
        UserRespDTO result = new UserRespDTO();

        // 将 UserDO 对象的属性值拷贝到 UserRespDTO 对象
        BeanUtils.copyProperties(userDO, result);

        // 返回结果
        return result;
    }

    @Override
    public Boolean hasUsername(String username) {
        // 调用布隆过滤器的 contains 方法，检查布隆过滤器中是否包含该用户名
        // 如果布隆过滤器中包含该用户名，返回 true；否则返回 false
        return userRegisterCachePenetrationBloomFilter.contains(username);
    }


    @Override
    public void register(UserRegisterReqDTO requestParam) {

        // 判断用户名是否已经存在
        if (hasUsername(requestParam.getUsername())) {
            // 如果用户名已存在，抛出用户名已存在的异常
            throw new ClientException(USER_NAME_EXIST);
        }

        // 获取Redis分布式锁
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + requestParam.getUsername());
        try {
            // 尝试获取锁，如果获取成功，则进行注册操作
            if (lock.tryLock()) {

                try{
                    // 将注册请求参数转化为 UserDO 实体类，并插入数据库
                    int inserted = baseMapper.insert(BeanUtil.toBean(requestParam, UserDO.class));
                    // 如果插入数据库失败（插入的行数小于1），则抛出用户名已存在异常
                    if (inserted < 1) {
                        throw new ClientException(USER_NAME_EXIST);
                    }
                }catch (DuplicateKeyException ex){
                    throw new ClientException(USER_EXIST);
                }
                // 将用户名添加到注册缓存穿透布隆过滤器中，防止重复注册
                userRegisterCachePenetrationBloomFilter.add(requestParam.getUsername());
                return;  // 注册成功，直接返回
            }
            // 如果无法获取到锁，抛出用户名已存在的异常
            throw new ClientException(USER_NAME_EXIST);
        } finally {
            // 无论是否成功，都需要释放锁
            lock.unlock();
        }
    }

    @Override
    public void update(UserUpdateReqDTO requestParam) {
        // TODO 验证当前用户是否为登录用户
        // TODO 逻辑：登录的用户是张三不可以修改李四的信息
        LambdaUpdateWrapper<UserDO> updateWrapper = Wrappers.lambdaUpdate(UserDO.class).eq(UserDO::getUsername, requestParam.getUsername());
        baseMapper.update(BeanUtil.toBean(requestParam, UserDO.class), updateWrapper);
    }


    @Override
// 重写接口中的 login 方法，处理用户登录逻辑
    public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
        // 创建一个 LambdaQueryWrapper 用于构建查询条件
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                // 查询条件：用户名与密码匹配
                .eq(UserDO::getUsername, requestParam.getUsername())
                .eq(UserDO::getPassword, requestParam.getPassword())
                // 用户未被注销的条件（delFlag 为 0）
                .eq(UserDO::getDelFlag, 0);

        // 使用 baseMapper（通常是 MyBatis-Plus 提供的）执行查询，获取符合条件的单个用户记录
        UserDO userDO = baseMapper.selectOne(queryWrapper);

        // 如果查询结果为 null，说明用户不存在，抛出自定义异常
        if (userDO == null) {
            throw new ClientException("用户不存在");
        }

        // 检查 Redis 中是否已有该用户的登录记录
        Boolean hasLogin = stringRedisTemplate.hasKey("login_" + requestParam.getUsername());

        // 如果 Redis 中存在该用户的登录记录，抛出异常，表示该用户已经登录
        if (hasLogin != null && hasLogin) {
            throw new ClientException("用户已登录");
        }

        // 生成一个唯一的 UUID，作为本次登录的标识
        String uuid = UUID.randomUUID().toString();

        // 将用户信息存入 Redis 哈希表中，用户名作为哈希表的键，uuid 作为字段名，用户对象序列化成 JSON 字符串为值
        stringRedisTemplate.opsForHash().put("login_" + requestParam.getUsername(), uuid, JSON.toJSONString(userDO));

        // 设置该用户名的 Redis 键的过期时间为 30 分钟，防止存储无限增长
        // 我们修改为30DAYS甚至更长
        stringRedisTemplate.expire("login_" + requestParam.getUsername(), 30L, TimeUnit.DAYS);

        // 返回登录响应，包含生成的 UUID
        return new UserLoginRespDTO(uuid);
    }


    @Override
    public Boolean checkLogin(String username, String token) {
        // 使用 stringRedisTemplate 访问 Redis 数据库
        // opsForHash() 获取对 Hash 数据结构的操作对象
        // 使用 "login_" + username 作为 Hash 的键
        // 使用 token 作为 Hash 中的字段（field），调用 get() 方法获取该字段的值
        // 如果字段存在，则返回该字段的值（非 null），否则返回 null
        return stringRedisTemplate.opsForHash().get("login_" + username, token) != null;
        // 如果获取到的值非 null，表示登录有效，返回 true；否则，返回 false，表示无效登录
    }

    @Override
    public void logout(String username, String token){
        if(checkLogin(username,token)){
            stringRedisTemplate.delete("login_"+username);
            return;
        }
        throw new ClientException("该用户Token不存在或者用户未登录");
    }


}
